package td;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;

import ui.MapView;
import ui.Viewable;

public class Map implements DoTListener {
	public ArrayList<ArrayList<Field>> fields;
	public ArrayList<Wave> waves;
	public Player player;
	public ArrayList<Enemy> dotted;
	public int waveCounter;
	public Field start;
	public Field end;
	public boolean isOver;
	public Viewable viewable;
	public int vineTrapCD;
	public int windTrapCD;
	public int fireTrapCD;

	public Map(String mapfile, String wavefile) throws IOException {
		/* Creating the player with 150 mana and 20 health. */
		player = new Player(150, 20);
		waveCounter = 0;
		isOver = false;
		/* Initializing the lists. */
		fields = new ArrayList<ArrayList<Field>>();
		waves = new ArrayList<Wave>();
		dotted = new ArrayList<Enemy>();
		/* After crating the player the map should be filled with fields. */
		this.setUpFields(mapfile);
		/* After that, we create enemy waves for the map */
		this.setUpWaves(wavefile);
		viewable = new MapView(this);
	}

	public void setUpFields(String file) throws IOException {
		/*
		 * Reads the mapfile, and decodes it to put either Field, Road,
		 * StartPointor endPoint.
		 */
		FileReader reader = new FileReader(file);
		BufferedReader input = new BufferedReader(reader);
		String line;
		while ((line = input.readLine()) != null) {
			String[] mapline = line.split(" ");
			ArrayList<Field> newRow = new ArrayList<Field>();
			for (int i = 0; i < mapline.length; i++) {
				newRow.add(fieldDecoder(mapline[i]));
			}
			fields.add(newRow);
		}
		input.close();
		/*
		 * After reading in the fields, we set up neighbours. If we use the
		 * prototype testing map, we don't do this automatically, since
		 * neighbour connecting is done there manually for testing.
		 */
		if (!file.equals("Files/protomap.txt")) {
			for (int i = 0; i < fields.size(); i++) {
				for (int j = 0; j < fields.get(i).size(); j++) {
					if (i == 0) {
						if (j == 0) {
							fields.get(i).get(j).neighbours.add(fields.get(
									i + 1).get(j));
							fields.get(i).get(j).neighbours.add(fields.get(i)
									.get(j + 1));
						} else if (j == fields.get(i).size() - 1) {
							fields.get(i).get(j).neighbours.add(fields.get(i)
									.get(j - 1));
							fields.get(i).get(j).neighbours.add(fields.get(
									i + 1).get(j));
						} else {
							fields.get(i).get(j).neighbours.add(fields.get(i)
									.get(j - 1));
							fields.get(i).get(j).neighbours.add(fields.get(
									i + 1).get(j));
							fields.get(i).get(j).neighbours.add(fields.get(i)
									.get(j + 1));
						}
					} else if (i == fields.size() - 1) {
						if (j == 0) {
							fields.get(i).get(j).neighbours.add(fields.get(
									i - 1).get(j));
							fields.get(i).get(j).neighbours.add(fields.get(i)
									.get(j + 1));
						} else if (j == fields.get(i).size() - 1) {
							fields.get(i).get(j).neighbours.add(fields.get(i)
									.get(j - 1));
							fields.get(i).get(j).neighbours.add(fields.get(
									i - 1).get(j));
						} else {
							fields.get(i).get(j).neighbours.add(fields.get(i)
									.get(j - 1));
							fields.get(i).get(j).neighbours.add(fields.get(
									i - 1).get(j));
							fields.get(i).get(j).neighbours.add(fields.get(i)
									.get(j + 1));
						}
					} else {
						if (j == 0) {
							fields.get(i).get(j).neighbours.add(fields.get(
									i - 1).get(j));
							fields.get(i).get(j).neighbours.add(fields.get(i)
									.get(j + 1));
							fields.get(i).get(j).neighbours.add(fields.get(
									i + 1).get(j));
						} else if (j == fields.get(i).size() - 1) {
							fields.get(i).get(j).neighbours.add(fields.get(
									i + 1).get(j));
							fields.get(i).get(j).neighbours.add(fields.get(i)
									.get(j - 1));
							fields.get(i).get(j).neighbours.add(fields.get(
									i - 1).get(j));
						} else {
							fields.get(i).get(j).neighbours.add(fields.get(i)
									.get(j - 1));
							fields.get(i).get(j).neighbours.add(fields.get(
									i - 1).get(j));
							fields.get(i).get(j).neighbours.add(fields.get(i)
									.get(j + 1));
							fields.get(i).get(j).neighbours.add(fields.get(
									i + 1).get(j));
						}
					}
				}
			}
			/*
			 * After setting up neighbours, we set up steppable fields from
			 * Start
			 */
			ArrayList<Field> todo = new ArrayList<Field>();
			ArrayList<Field> checked = new ArrayList<Field>();
			checked.add(start);
			for (int i = 0; i < start.neighbours.size(); i++) {
				if (start.neighbours.get(i).isRoad) {
					todo.add(start.neighbours.get(i));
					start.nextSteppable.add(start.neighbours.get(i));
				}
			}
			int j = 0;
			Field current = todo.get(j);
			while (true) {
				for (int i = 0; i < current.neighbours.size(); i++) {
					if (current.neighbours.contains(end)) {
						current.nextSteppable.add(end);
					} else if (current.neighbours.get(i).isRoad
							&& current.neighbours.get(i).nextSteppable
									.contains(end)) {
						current.nextSteppable.add(current.neighbours.get(i));
					} else if (current.neighbours.get(i).isRoad
							&& !checked.contains(current.neighbours.get(i))) {
						todo.add(current.neighbours.get(i));
						current.nextSteppable.add(current.neighbours.get(i));
					}
				}
				checked.add(current);
				if (j < todo.size() - 1) {
					j++;
					current = todo.get(j);
				} else {
					break;
				}
			}
		}
		/*
		 * "Removed" code. It's duty was to remove dead ends from the road if
		 * there was any. Since it's not really essential in a tower defense to
		 * work with dead ends, we removed the code, to save compute time.
		 */
		/*
		 * int unfilled = 1; while (unfilled != 0) { unfilled = 0; for (int c =
		 * 0; c < todo.size(); c++) { if (todo.get(c).nextSteppable.isEmpty()) {
		 * System.out.println(todo.indexOf(todo.get(c))); unfilled++; int max =
		 * 0; for (int n = 0; n < todo.get(c).neighbours.size(); n++) { if
		 * (todo.contains(todo.get(c).neighbours.get(n))) { if
		 * (todo.indexOf(todo.get(c).neighbours.get(n)) > max) { max =
		 * todo.indexOf(todo.get(c).neighbours.get(n)); } } } if
		 * (todo.get(max).nextSteppable.contains(todo.get(c))) {
		 * todo.get(max).nextSteppable.remove(todo.get(c));
		 * todo.get(c).nextSteppable.add(todo.get(max)); } } } }
		 */
	}

	/* Reads file to create given enemies and add them to the wave list */
	public void setUpWaves(String file) throws IOException {
		FileReader reader = new FileReader(file);
		BufferedReader input = new BufferedReader(reader);
		String line;
		while ((line = input.readLine()) != null) {
			String[] waveline = line.split(" ");
			Wave wave = new Wave(Integer.parseInt(waveline[0]));
			for (int i = 1; i < waveline.length; i++) {
				Enemy enemy = enemyDecoder(waveline[i]);
				if(enemy != null)
					wave.enemies.add(enemy);
			}
			waves.add(wave);
		}
		input.close();
	}

	/*
	 * Reads enemies from file, and decodes which character is which enemy in
	 * the file
	 */
	public Enemy enemyDecoder(String s) {
		if (s.equals("D"))
			return new Dwarf(start, this);
		else if (s.equals("E"))
			return new Elf(start, this);
		else if (s.equals("N"))
			return new Ent(start, this);
		else if (s.equals("G"))
			return new Ghost(start, this);
		else if (s.equals("H"))
			return new Hobbit(start, this);
		else if (s.equals("M"))
			return new Human(start, this);
		else
			return null;
	}

	/* Reads mapfile and decodes whether it is a Field,Road,Start-,or EndPoint */
	public Field fieldDecoder(String s) {
		if (s.equals("R")) {
			Field road = new Field();
			road.isRoad = true;
			road.viewable.update();
			return road;
		} else if (s.equals("S")) {
			Field start = new Field();
			start.isRoad = true;
			start.isStartPoint = true;
			start.viewable.update();
			this.start = start;
			return start;
		} else if (s.equals("M")) {
			Field end = new Field();
			end.isRoad = true;
			end.isEndPoint = true;
			end.viewable.update();
			this.end = end;
			return end;
		} else if (s.equals("A")) {
			Field f = new Field();
			player.pickTower(new ArcherTower(f));
			player.placeTower(f);
			f.viewable.update();
			return f;
		} else if (s.equals("C")) {
			Field f = new Field();
			player.pickTower(new CatapultTower(f));
			player.placeTower(f);
			f.viewable.update();
			return f;
		} else if (s.equals("O")) {
			Field f = new Field();
			player.pickTower(new OneTower(f));
			player.placeTower(f);
			f.viewable.update();
			return f;
		} else if (s.equals("T")) {
			Field f = new Field();
			player.pickTower(new TwoTower(f));
			player.placeTower(f);
			f.viewable.update();
			return f;
		} else if (s.equals("F")) {
			Field f = new Field();
			f.isRoad = true;
			player.pickTrap(new FireTrap(f));
			player.placeTrap(f);
			f.viewable.update();
			return f;
		} else if (s.equals("W")) {
			Field f = new Field();
			f.isRoad = true;
			player.pickTrap(new WindTrap(f));
			player.placeTrap(f);
			f.viewable.update();
			return f;
		} else if (s.equals("V")) {
			Field f = new Field();
			f.isRoad = true;
			player.pickTrap(new VineTrap(f));
			player.placeTrap(f);
			f.viewable.update();
			return f;
		} else {
			return new Field();
		}
	}

	public void enemyDied(Enemy enemy) {
		/* Gaining mana after defeating an enemy. */
		player.gainMana(enemy);
		waves.get(waveCounter).enemies.remove(enemy);
		
	}

	public void enemyHasDoT(Enemy enemy) {
		/* Adding a dotted enemy to a list. */
		dotted.add(enemy);
	}

	public void enemyWon(Enemy enemy) {
		/* Decreasing the players health depending on the enemy. */
		player.loseHp(enemy);
		waves.get(waveCounter).enemies.remove(enemy);
		if (player.getHp() <= 0)
			gameOver();
	}

	/*
	 * Checks if we can still send waves, or the player have won by beating all
	 * waves.
	 */
	public int sendNextWave() {
		waveCounter++;
		if (waveCounter < waves.size())
			return waveCounter;
		else {
			gameOver();
			return waveCounter;
		}

	}

	public void gameOver() {
		isOver = true;
	}

	public String getReason(){
		if (player.getHp() <= 0)
			return "Out of hp. Sauron have failed to rule the world!";
		else
			return "Attack waves of the alliance beaten. Sauron has been saved!";
	}
	
	public void cutInHalf(Enemy cutted, Enemy firsthalf, Enemy secondhalf) {
		waves.get(waveCounter).enemies.remove(cutted);
		waves.get(waveCounter).enemies.add(firsthalf);
		waves.get(waveCounter).enemies.add(secondhalf);

	}

	public void steppedInVineTrap(VineTrap trap) {
		player.vineTraps.remove(trap);
	}

	public void steppedInWindTrap(WindTrap trap) {
		player.windTraps.remove(trap);
	}

	public void steppedInFireTrap(FireTrap trap) {
		player.fireTraps.remove(trap);
	}
}
